package edu.udo.sopra10.chaturaji.entities;

import static edu.udo.sopra10.chaturaji.util.ExceptionUtil.assertInRange;
import static edu.udo.sopra10.chaturaji.util.ExceptionUtil.assertNotNull;
import static edu.udo.sopra10.chaturaji.util.ExceptionUtil.assertNotNullOrContainsNull;
import static java.util.Collections.unmodifiableCollection;
import static org.apache.commons.lang.builder.ToStringStyle.SHORT_PREFIX_STYLE;

import java.util.ArrayList;
import java.util.Collection;

import org.apache.commons.lang.builder.ToStringBuilder;

/**
 * A turn corresponds to two {@link Move}s. This contains the entire action
 * performed by a {@link Player} during her turn.
 * 
 * @author Simon Dierl
 */
public final class Turn {
	/**
	 * The first move.
	 */
	private Move firstMove;

	/**
	 * The player who made the turn.
	 */
	private Player player;

	/**
	 * The second move.
	 */
	private Move secondMove;

	/**
	 * The rolls that have not been assigned to moves.
	 */
	private final Collection<Byte> unassignedRolls = new ArrayList<Byte>(2);

	/**
	 * Create a new turn consisting from two {@link Move}s and a playing
	 * {@link Player}.
	 * 
	 * @param player
	 *            the playing player, must not be {@code null}.
	 * @param firstMove
	 *            the first move, {@code null} if not made yet.
	 * @param secondMove
	 *            the second move, {@code null} if not made yet.
	 */
	public Turn(final Player player, final Move firstMove, final Move secondMove) {
		setPlayer(player);
		setMoves(firstMove, secondMove);
	}

	/**
	 * Add a roll to the collection of unassigned rolls.
	 * 
	 * @param roll
	 *            the new roll, must be in {@code [1, 6]}.
	 * @return if the roll was new to the collection. Always returns
	 *         {@code true}.
	 */
	public boolean addUnassignedRoll(final byte roll) {
		return unassignedRolls.add(assertInRange(roll, (byte) 1, (byte) 6,
				"roll"));
	}

	/**
	 * Add several rolls to the collection of unassigned rolls.
	 * 
	 * @param rolls
	 *            the new rolls, must not be {@code null} or contain {code null}
	 *            and be in {@code [1, 6]}. This collection is copied to
	 *            preserve encapsulation.
	 * @return if one if the rolls was new to the collection. Always returns
	 *         {@code !rolls.isEmpty()}.
	 */
	public boolean addUnassignedRolls(final Collection<Byte> rolls) {
		boolean ret = false;
		for (final byte roll : assertNotNullOrContainsNull(rolls, "rolls")) {
			ret |= addUnassignedRoll(roll);
		}
		return ret;
	}

	/**
	 * Remove all rolls from the collection of unassigned rolls.
	 */
	public void clearUnassignedRolls() {
		unassignedRolls.clear();
	}

	/**
	 * Get the first move.
	 * 
	 * @return the first move or {@code null} if not made yet.
	 */
	public Move getFirstMove() {
		return firstMove;
	}

	/**
	 * Get the playing player.
	 * 
	 * @return the playing player, never {@code null}.
	 */
	public Player getPlayer() {
		return player;
	}

	/**
	 * Get the second move.
	 * 
	 * @return the second move or {@code null} if not made yet.
	 */
	public Move getSecondMove() {
		return secondMove;
	}

	/**
	 * Get all rolls that have not been assigned to {@link PieceMove}s. These
	 * might either be really unused or assigned to {@link Pass}es.
	 * 
	 * @return all unassigned rolls.
	 */
	public Collection<Byte> getUnassignedRolls() {
		return unmodifiableCollection(unassignedRolls);
	}

	/**
	 * Remove a roll from the collection of unassigned rolls.
	 * 
	 * @param roll
	 *            the roll to remove, must not be {@code null}.
	 * @return if the roll was contained in the collection.
	 */
	public boolean removeUnassignedRoll(final byte roll) {
		return unassignedRolls.remove(roll);
	}

	/**
	 * Remove several rolls from the collection of unassigned rolls.
	 * 
	 * @param rolls
	 *            the rolls to remove, must not be {@code null} or contain
	 *            {@code null}.
	 * @return if any of the rolls were contained in the collection.
	 */
	public boolean removeUnassignedRolls(final Collection<Byte> rolls) {
		boolean ret = false;
		for (final byte roll : assertNotNullOrContainsNull(rolls, "rolls")) {
			ret |= removeUnassignedRoll(roll);
		}
		return ret;
	}

	/**
	 * Set the first move.
	 * 
	 * @param firstMove
	 *            the new first move, {@code null} if not made yet.
	 */
	public void setFirstMove(final Move firstMove) {
		this.firstMove = firstMove;
	}

	/**
	 * Set both moves.
	 * 
	 * @param firstMove
	 *            the new first move, {@code null} if not made yet.
	 * @param secondMove
	 *            the new second move, {@code null} if not made yet.
	 */
	public void setMoves(final Move firstMove, final Move secondMove) {
		setFirstMove(firstMove);
		setSecondMove(secondMove);
	}

	/**
	 * Set the playing player.
	 * 
	 * @param player
	 *            the new playing player, must not be {@code null}.
	 */
	public void setPlayer(final Player player) {
		this.player = assertNotNull(player, "player");
	}

	/**
	 * Set the second move.
	 * 
	 * @param secondMove
	 *            the new second move, {@code null} if not made yet.
	 */
	public void setSecondMove(final Move secondMove) {
		this.secondMove = secondMove;
	}

	@Override
	public String toString() {
		return new ToStringBuilder(this, SHORT_PREFIX_STYLE)
				.append("player", player).append("firstMove", firstMove)
				.append("secondMove", secondMove).toString();
	}
}
